# -*- coding: utf-8 -*-
"""
安全工具模块

提供密码加密、令牌生成、数据脱敏等安全相关功能。
确保系统的安全性和数据保护。
"""

import hashlib
import hmac
import secrets
import string
import base64
import re
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, Union, List
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import bcrypt
from jose import jwt

from ..utils.exceptions import AuthenticationError, SecurityError


class PasswordManager:
    """密码管理器"""
    
    @staticmethod
    def hash_password(password: str) -> str:
        """哈希密码"""
        if not password:
            raise ValueError("密码不能为空")
        
        # 使用bcrypt进行密码哈希
        salt = bcrypt.gensalt()
        hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
        return hashed.decode('utf-8')
    
    @staticmethod
    def verify_password(password: str, hashed: str) -> bool:
        """验证密码"""
        if not password or not hashed:
            return False
        
        try:
            return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
        except Exception:
            return False
    
    @staticmethod
    def generate_password(length: int = 12, include_symbols: bool = True) -> str:
        """生成随机密码"""
        if length < 8:
            raise ValueError("密码长度不能少于8位")
        
        # 字符集
        lowercase = string.ascii_lowercase
        uppercase = string.ascii_uppercase
        digits = string.digits
        symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?" if include_symbols else ""
        
        # 确保至少包含各类字符
        password = [
            secrets.choice(lowercase),
            secrets.choice(uppercase),
            secrets.choice(digits),
        ]
        
        if include_symbols:
            password.append(secrets.choice(symbols))
        
        # 填充剩余长度
        all_chars = lowercase + uppercase + digits + symbols
        for _ in range(length - len(password)):
            password.append(secrets.choice(all_chars))
        
        # 打乱顺序
        secrets.SystemRandom().shuffle(password)
        
        return ''.join(password)
    
    @staticmethod
    def check_password_strength(password: str) -> Dict[str, Any]:
        """检查密码强度"""
        result = {
            'score': 0,
            'strength': 'weak',
            'suggestions': [],
            'checks': {
                'length': False,
                'lowercase': False,
                'uppercase': False,
                'digits': False,
                'symbols': False,
                'no_common': False
            }
        }
        
        # 长度检查
        if len(password) >= 8:
            result['checks']['length'] = True
            result['score'] += 1
        else:
            result['suggestions'].append("密码长度至少8位")
        
        # 小写字母
        if re.search(r'[a-z]', password):
            result['checks']['lowercase'] = True
            result['score'] += 1
        else:
            result['suggestions'].append("包含小写字母")
        
        # 大写字母
        if re.search(r'[A-Z]', password):
            result['checks']['uppercase'] = True
            result['score'] += 1
        else:
            result['suggestions'].append("包含大写字母")
        
        # 数字
        if re.search(r'\d', password):
            result['checks']['digits'] = True
            result['score'] += 1
        else:
            result['suggestions'].append("包含数字")
        
        # 特殊字符
        if re.search(r'[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]', password):
            result['checks']['symbols'] = True
            result['score'] += 1
        else:
            result['suggestions'].append("包含特殊字符")
        
        # 常见密码检查
        common_passwords = ['password', '123456', 'qwerty', 'abc123', 'password123']
        if password.lower() not in common_passwords:
            result['checks']['no_common'] = True
            result['score'] += 1
        else:
            result['suggestions'].append("避免使用常见密码")
        
        # 评级
        if result['score'] >= 5:
            result['strength'] = 'strong'
        elif result['score'] >= 3:
            result['strength'] = 'medium'
        else:
            result['strength'] = 'weak'
        
        return result


class TokenManager:
    """令牌管理器"""
    
    def __init__(self, secret_key: str):
        self.secret_key = secret_key
    
    def generate_token(self, payload: Dict[str, Any], expires_in: int = 3600) -> str:
        """生成JWT令牌"""
        now = datetime.utcnow()
        payload.update({
            'iat': now,
            'exp': now + timedelta(seconds=expires_in),
            'jti': secrets.token_hex(16)  # JWT ID
        })
        
        return jwt.encode(payload, self.secret_key, algorithm='HS256')
    
    def verify_token(self, token: str) -> Dict[str, Any]:
        """验证JWT令牌"""
        try:
            payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
            return payload
        except jwt.ExpiredSignatureError:
            raise AuthenticationError("令牌已过期")
        except jwt.InvalidTokenError:
            raise AuthenticationError("无效的令牌")
    
    def refresh_token(self, token: str, expires_in: int = 3600) -> str:
        """刷新令牌"""
        payload = self.verify_token(token)
        
        # 移除时间相关字段
        payload.pop('iat', None)
        payload.pop('exp', None)
        payload.pop('jti', None)
        
        return self.generate_token(payload, expires_in)
    
    @staticmethod
    def generate_random_token(length: int = 32) -> str:
        """生成随机令牌"""
        return secrets.token_urlsafe(length)
    
    @staticmethod
    def generate_api_key(prefix: str = "crm") -> str:
        """生成API密钥"""
        random_part = secrets.token_urlsafe(24)
        return f"{prefix}_{random_part}"


class DataEncryption:
    """数据加密器"""
    
    def __init__(self, password: str):
        self.password = password.encode()
        self.salt = b'salt_1234567890'  # 在实际应用中应该使用随机盐
        self.key = self._derive_key()
        self.cipher = Fernet(self.key)
    
    def _derive_key(self) -> bytes:
        """派生密钥"""
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=self.salt,
            iterations=100000,
        )
        key = base64.urlsafe_b64encode(kdf.derive(self.password))
        return key
    
    def encrypt(self, data: str) -> str:
        """加密数据"""
        if not data:
            return ""
        
        encrypted = self.cipher.encrypt(data.encode())
        return base64.urlsafe_b64encode(encrypted).decode()
    
    def decrypt(self, encrypted_data: str) -> str:
        """解密数据"""
        if not encrypted_data:
            return ""
        
        try:
            encrypted = base64.urlsafe_b64decode(encrypted_data.encode())
            decrypted = self.cipher.decrypt(encrypted)
            return decrypted.decode()
        except Exception:
            raise SecurityError("数据解密失败")
    
    def encrypt_dict(self, data: Dict[str, Any], fields: List[str]) -> Dict[str, Any]:
        """加密字典中的指定字段"""
        result = data.copy()
        for field in fields:
            if field in result and result[field]:
                result[field] = self.encrypt(str(result[field]))
        return result
    
    def decrypt_dict(self, data: Dict[str, Any], fields: List[str]) -> Dict[str, Any]:
        """解密字典中的指定字段"""
        result = data.copy()
        for field in fields:
            if field in result and result[field]:
                result[field] = self.decrypt(result[field])
        return result


class DataMasking:
    """数据脱敏器"""
    
    @staticmethod
    def mask_phone(phone: str) -> str:
        """脱敏手机号"""
        if not phone or len(phone) < 11:
            return phone
        
        return phone[:3] + "*" * 4 + phone[7:]
    
    @staticmethod
    def mask_email(email: str) -> str:
        """脱敏邮箱"""
        if not email or "@" not in email:
            return email
        
        local, domain = email.split("@", 1)
        if len(local) <= 2:
            return email
        
        masked_local = local[0] + "*" * (len(local) - 2) + local[-1]
        return f"{masked_local}@{domain}"
    
    @staticmethod
    def mask_id_card(id_card: str) -> str:
        """脱敏身份证号"""
        if not id_card or len(id_card) < 18:
            return id_card
        
        return id_card[:6] + "*" * 8 + id_card[14:]
    
    @staticmethod
    def mask_name(name: str) -> str:
        """脱敏姓名"""
        if not name or len(name) < 2:
            return name
        
        if len(name) == 2:
            return name[0] + "*"
        else:
            return name[0] + "*" * (len(name) - 2) + name[-1]
    
    @staticmethod
    def mask_address(address: str) -> str:
        """脱敏地址"""
        if not address or len(address) < 6:
            return address
        
        return address[:3] + "*" * (len(address) - 6) + address[-3:]
    
    @staticmethod
    def mask_bank_card(card_number: str) -> str:
        """脱敏银行卡号"""
        if not card_number or len(card_number) < 8:
            return card_number
        
        return card_number[:4] + "*" * (len(card_number) - 8) + card_number[-4:]
    
    @staticmethod
    def mask_custom(data: str, start_keep: int = 2, end_keep: int = 2, mask_char: str = "*") -> str:
        """自定义脱敏"""
        if not data or len(data) <= start_keep + end_keep:
            return data
        
        start = data[:start_keep]
        end = data[-end_keep:] if end_keep > 0 else ""
        middle = mask_char * (len(data) - start_keep - end_keep)
        
        return start + middle + end


class SecurityValidator:
    """安全验证器"""
    
    @staticmethod
    def validate_ip_address(ip: str) -> bool:
        """验证IP地址"""
        pattern = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
        if not pattern.match(ip):
            return False
        
        parts = ip.split('.')
        return all(0 <= int(part) <= 255 for part in parts)
    
    @staticmethod
    def is_safe_filename(filename: str) -> bool:
        """检查文件名是否安全"""
        # 危险字符和路径
        dangerous_chars = ['..', '/', '\\', ':', '*', '?', '"', '<', '>', '|']
        dangerous_extensions = ['.exe', '.bat', '.cmd', '.com', '.scr', '.vbs', '.js']
        
        # 检查危险字符
        for char in dangerous_chars:
            if char in filename:
                return False
        
        # 检查危险扩展名
        lower_filename = filename.lower()
        for ext in dangerous_extensions:
            if lower_filename.endswith(ext):
                return False
        
        return True
    
    @staticmethod
    def sanitize_sql_input(input_str: str) -> str:
        """清理SQL输入"""
        if not input_str:
            return ""
        
        # 移除SQL注入相关字符
        dangerous_chars = ["'", '"', ';', '--', '/*', '*/', 'xp_', 'sp_']
        result = input_str
        
        for char in dangerous_chars:
            result = result.replace(char, '')
        
        return result
    
    @staticmethod
    def validate_url(url: str) -> bool:
        """验证URL"""
        pattern = re.compile(
            r'^https?://'  # http:// or https://
            r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'  # domain
            r'localhost|'  # localhost
            r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # IP
            r'(?::\d+)?'  # optional port
            r'(?:/?|[/?]\S+)$', re.IGNORECASE)
        return pattern.match(url) is not None


class SessionManager:
    """会话管理器"""
    
    def __init__(self, secret_key: str):
        self.secret_key = secret_key
        self.sessions: Dict[str, Dict[str, Any]] = {}
    
    def create_session(self, user_id: int, data: Dict[str, Any] = None) -> str:
        """创建会话"""
        session_id = secrets.token_urlsafe(32)
        
        session_data = {
            'user_id': user_id,
            'created_at': datetime.utcnow(),
            'last_access': datetime.utcnow(),
            'data': data or {}
        }
        
        self.sessions[session_id] = session_data
        return session_id
    
    def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
        """获取会话"""
        session = self.sessions.get(session_id)
        if session:
            session['last_access'] = datetime.utcnow()
        return session
    
    def update_session(self, session_id: str, data: Dict[str, Any]) -> bool:
        """更新会话"""
        if session_id in self.sessions:
            self.sessions[session_id]['data'].update(data)
            self.sessions[session_id]['last_access'] = datetime.utcnow()
            return True
        return False
    
    def delete_session(self, session_id: str) -> bool:
        """删除会话"""
        if session_id in self.sessions:
            del self.sessions[session_id]
            return True
        return False
    
    def cleanup_expired_sessions(self, max_age: int = 3600) -> int:
        """清理过期会话"""
        cutoff_time = datetime.utcnow() - timedelta(seconds=max_age)
        expired_sessions = []
        
        for session_id, session_data in self.sessions.items():
            if session_data['last_access'] < cutoff_time:
                expired_sessions.append(session_id)
        
        for session_id in expired_sessions:
            del self.sessions[session_id]
        
        return len(expired_sessions)


class AuditLogger:
    """审计日志记录器"""
    
    def __init__(self, logger_name: str = "audit"):
        import logging
        self.logger = logging.getLogger(logger_name)
    
    def log_login(self, user_id: int, username: str, ip_address: str, success: bool):
        """记录登录日志"""
        status = "SUCCESS" if success else "FAILED"
        self.logger.info(f"LOGIN {status} - User: {username} ({user_id}), IP: {ip_address}")
    
    def log_logout(self, user_id: int, username: str, ip_address: str):
        """记录登出日志"""
        self.logger.info(f"LOGOUT - User: {username} ({user_id}), IP: {ip_address}")
    
    def log_data_access(self, user_id: int, table_name: str, record_id: Any, action: str):
        """记录数据访问日志"""
        self.logger.info(f"DATA ACCESS - User: {user_id}, Table: {table_name}, Record: {record_id}, Action: {action}")
    
    def log_permission_change(self, admin_id: int, target_user_id: int, old_permissions: str, new_permissions: str):
        """记录权限变更日志"""
        self.logger.info(f"PERMISSION CHANGE - Admin: {admin_id}, Target: {target_user_id}, Old: {old_permissions}, New: {new_permissions}")
    
    def log_security_event(self, event_type: str, details: Dict[str, Any]):
        """记录安全事件日志"""
        self.logger.warning(f"SECURITY EVENT - Type: {event_type}, Details: {details}")


# 全局实例
def get_password_manager() -> PasswordManager:
    """获取密码管理器实例"""
    return PasswordManager()


def get_token_manager(secret_key: str) -> TokenManager:
    """获取令牌管理器实例"""
    return TokenManager(secret_key)


def get_data_encryption(password: str) -> DataEncryption:
    """获取数据加密器实例"""
    return DataEncryption(password)


def get_data_masking() -> DataMasking:
    """获取数据脱敏器实例"""
    return DataMasking()


def get_security_validator() -> SecurityValidator:
    """获取安全验证器实例"""
    return SecurityValidator()


def get_session_manager(secret_key: str) -> SessionManager:
    """获取会话管理器实例"""
    return SessionManager(secret_key)


def get_audit_logger() -> AuditLogger:
    """获取审计日志记录器实例"""
    return AuditLogger()


# 便捷函数
def hash_password(password: str) -> str:
    """哈希密码"""
    return PasswordManager.hash_password(password)


def verify_password(password: str, hashed: str) -> bool:
    """验证密码"""
    return PasswordManager.verify_password(password, hashed)


def generate_password(length: int = 12, include_symbols: bool = True) -> str:
    """生成随机密码"""
    return PasswordManager.generate_password(length, include_symbols)


def mask_sensitive_data(data: str, data_type: str = "phone") -> str:
    """脱敏敏感数据"""
    masking = DataMasking()
    
    if data_type == "phone":
        return masking.mask_phone(data)
    elif data_type == "email":
        return masking.mask_email(data)
    elif data_type == "id_card":
        return masking.mask_id_card(data)
    elif data_type == "name":
        return masking.mask_name(data)
    elif data_type == "address":
        return masking.mask_address(data)
    else:
        return masking.mask_custom(data)